/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.internal.jta;

/**
 * <p>
 * TransactionManager: A JTA compatible Transaction Manager.
 * </p>
 * 
 * @since GemFire 4.1.1
 */
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.geode.CancelException;
import org.apache.geode.i18n.LogWriterI18n;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.LoggingThreadGroup;

public class TransactionManagerImpl implements TransactionManager, Serializable {
  private static final long serialVersionUID = 5033392316185449821L;

  /**
   * A mapping of Thread - Transaction Objects
   */
  private Map transactionMap = new ConcurrentHashMap();
  /**
   * A mapping of Transaction - Global Transaction
   */
  private Map globalTransactionMap = Collections.synchronizedMap(new HashMap());
  /**
   * Ordered set of active global transactions - Used for timeOut
   */
  protected SortedSet gtxSet = Collections.synchronizedSortedSet(new TreeSet(
      new GlobalTransactionComparator()));
  /**
   * Transaction TimeOut Class
   */
  transient private TransactionTimeOutThread cleaner;
  /**
   * Singleton transactionManager
   */
  private static TransactionManagerImpl transactionManager = null;
  /**
   * Transaction TimeOut thread
   */
  transient private Thread cleanUpThread = null;
  /**
   * Default Transaction Time Out
   */
  static final int DEFAULT_TRANSACTION_TIMEOUT = Integer.getInteger("jta.defaultTimeout", 600).intValue();
  /**
   * Asif: The integers identifying the cause of Rollback
   */
  private static final int MARKED_ROLLBACK = 1;
  private static final int EXCEPTION_IN_NOTIFY_BEFORE_COMPLETION = 2;
  private static final int COMMIT_FAILED_SO_ROLLEDBAK = 3;
  private static final int COMMIT_FAILED_ROLLBAK_ALSO_FAILED = 4;
  private static final int ROLLBAK_FAILED = 5;
  //TODO:Asif .Not yet used this exception code
//  private static final int EXCEPTION_IN_NOTIFY_AFTER_COMPLETION = 6;
  /*
   * to enable VERBOSE = true pass System parameter jta.VERBOSE = true
   * while running the test. 
   */
  private static boolean VERBOSE = Boolean.getBoolean("jta.VERBOSE");
  /*
   * checks if the TransactionManager is active 
   */
  private boolean isActive = true;

  /**
   * Constructs a new TransactionManagerImpl
   */
  private TransactionManagerImpl() {
    cleaner = this.new TransactionTimeOutThread();
    ThreadGroup group =
      LoggingThreadGroup.createThreadGroup(LocalizedStrings.TransactionManagerImpl_CLEAN_UP_THREADS.toLocalizedString());
    cleanUpThread = new Thread(group, cleaner, "GlobalTXTimeoutMonitor");
    cleanUpThread.setDaemon(true);
    cleanUpThread.start();
  }

  /**
   * Returns the singleton TransactionManagerImpl Object
   */
  public static TransactionManagerImpl getTransactionManager() {
    if (transactionManager == null) {
      createTransactionManager();
    }
    return transactionManager;
  }

  /**
   * Creates an instance of TransactionManagerImpl if none exists
   */
  private static synchronized void createTransactionManager() {
    if (transactionManager == null)
        transactionManager = new TransactionManagerImpl();
  }

  /**
   * Create a new transaction and associate it with the current thread if none
   * exists with the current thread else throw an exception since nested
   * transactions are not supported
   * 
   * Create a global transaction and associate the transaction created with the
   * global transaction
   * 
   * @throws NotSupportedException - Thrown if the thread is already associated
   *             with a transaction and the Transaction Manager implementation
   *             does not support nested transactions.
   * @throws SystemException - Thrown if the transaction manager encounters an
   *             unexpected error condition.
   * 
   * @see javax.transaction.TransactionManager#begin()
   */
  public void begin() throws NotSupportedException, SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    LogWriterI18n log = TransactionUtils.getLogWriterI18n();
    if (log.fineEnabled()) {
      log.fine("TransactionManager.begin() invoked");
    }
    Thread thread = Thread.currentThread();
    if (transactionMap.get(thread) != null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_BEGIN_NESTED_TRANSACTION_IS_NOT_SUPPORTED.toLocalizedString();
      if (VERBOSE) log.fine(exception);
      throw new NotSupportedException(exception);
    }
    try {
      TransactionImpl transaction = new TransactionImpl();
      transactionMap.put(thread, transaction);
      GlobalTransaction globalTransaction = new GlobalTransaction();
      globalTransactionMap.put(transaction, globalTransaction);
      globalTransaction.addTransaction(transaction);
      globalTransaction.setStatus(Status.STATUS_ACTIVE);
    }
    catch (Exception e) {
      String exception = LocalizedStrings.TransactionManagerImpl_BEGIN__SYSTEMEXCEPTION_DUE_TO_0.toLocalizedString(new Object[] {e});
      if (log.severeEnabled()) log.severe(LocalizedStrings.TransactionManagerImpl_BEGIN__SYSTEMEXCEPTION_DUE_TO_0, new Object[] {e});
      throw new SystemException(exception);
    }
  }

  /**
   * Complete the transaction associated with the current thread by calling the
   * GlobalTransaction.commit(). When this method completes, the thread is no
   * longer associated with a transaction.
   * 
   * @throws RollbackException - Thrown to indicate that the transaction has
   *             been rolled back rather than committed.
   * @throws HeuristicMixedException - Thrown to indicate that a heuristic
   *             decision was made and that some relevant updates have been
   *             committed while others have been rolled back.
   * @throws HeuristicRollbackException - Thrown to indicate that a heuristic
   *             decision was made and that all relevant updates have been
   *             rolled back.
   * @throws java.lang.SecurityException - Thrown to indicate that the thread
   *             is not allowed to commit the transaction.
   * @throws java.lang.IllegalStateException - Thrown if the current thread is
   *             not associated with a transaction.
   * @throws SystemException - Thrown if the transaction manager encounters an
   *             unexpected error condition.
   * 
   * @see javax.transaction.TransactionManager#commit()
   */
  public void commit() throws HeuristicRollbackException, RollbackException,
      HeuristicMixedException, SystemException {
    if (!isActive) { 
      throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); 
    }
    int cozOfException = -1;
    Transaction transactionImpl = getTransaction();
    if (transactionImpl == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_TRANSACTION_IS_NULL_CANNOT_COMMIT_A_NULL_TRANSACTION.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new IllegalStateException(exception);
    }
    GlobalTransaction gtx = getGlobalTransaction(transactionImpl);
    if (gtx == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_GLOBAL_TRANSACTION_IS_NULL_CANNOT_COMMIT_A_NULL_GLOBAL_TRANSACTION.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    boolean isCommit = false;
    //ensure only one thread can commit. Use a synchronized block
    //Asif
    int status = -1;
    if (((status = gtx.getStatus()) == Status.STATUS_ACTIVE)
        || status == Status.STATUS_MARKED_ROLLBACK) {
      synchronized (gtx) {
        if ((status = gtx.getStatus()) == Status.STATUS_ACTIVE) {
          gtx.setStatus(Status.STATUS_COMMITTING);
          isCommit = true;
        }
        else if (status == Status.STATUS_MARKED_ROLLBACK) {
          gtx.setStatus(Status.STATUS_ROLLING_BACK);
          cozOfException = MARKED_ROLLBACK;
        }
        else {
          String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_TRANSACTION_NOT_ACTIVE_CANNOT_BE_COMMITTED_TRANSACTION_STATUS_0.toLocalizedString(Integer.valueOf(status));
          LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
          if (VERBOSE) writer.fine(exception);
          throw new IllegalStateException(exception);
        }
      }
    }
    else {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_TRANSACTION_IS_NOT_ACTIVE_AND_CANNOT_BE_COMMITTED.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new IllegalStateException(exception);
    }
    //Only one thread can call commit (the first thread to do reach the block
    // above).
    //Before commiting the notifications to be done before the done are called
    // the global transaction is called and then the after completion
    // notifications
    // are taken care of. The transactions associated to the global
    // transactions are
    //removed from the map and also the tread to transaction.
    //
    //  Asif : Store the thrown Exception in case of commit .
    //Reuse it for thrwing later.
    //Asif TODO:Verify if it is a good practise
    boolean isClean = false;
    Exception e = null;
    try {
      ((TransactionImpl) transactionImpl).notifyBeforeCompletion();
      isClean = true;
    }
    catch (Exception ge) {
      //Asif : Just mark the Tranxn to setRollbackOnly to ensure Rollback
      setRollbackOnly();
      cozOfException = EXCEPTION_IN_NOTIFY_BEFORE_COMPLETION;
      e = ge;
    }
    //TODO:Asif In case the status of transaction is marked as
    // ROLLING_BACK , then we don't have to take a synch block
    // As once the status is marked for ROLLING_BACK , setRollnbackonly
    //will be harmless
    if (isCommit) {
      synchronized (gtx) {
        if ((status = gtx.getStatus()) == Status.STATUS_COMMITTING) {
          //Asif: Catch any exception encountered during commit
          // and appropriately mark the exception code
          try {
            gtx.commit();
          }
          catch (RollbackException rbe) {
            e = rbe;
            cozOfException = COMMIT_FAILED_SO_ROLLEDBAK;
          }
          catch (SystemException se) {
            e = se;
            cozOfException = COMMIT_FAILED_ROLLBAK_ALSO_FAILED;
          }
        }
        else if (status == Status.STATUS_ROLLING_BACK) {
          try {
            gtx.rollback();
            if (isClean) cozOfException = MARKED_ROLLBACK;
          }
          catch (SystemException se) {
            e = se;
            cozOfException = ROLLBAK_FAILED;
          }
        }
      }
    }
    else {
      try {
        gtx.rollback();
      }
      catch (SystemException se) {
        e = se;
        cozOfException = ROLLBAK_FAILED;
      }
    }
    try {
      ((TransactionImpl) transactionImpl).notifyAfterCompletion(status = gtx
          .getStatus());
    }
    catch (Exception ge) {
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (writer.infoEnabled())
          writer.info(LocalizedStrings.TransactionManagerImpl_EXCEPTION_IN_NOTIFY_AFTER_COMPLETION_DUE_TO__0, ge.getMessage(), ge);
    }
    Thread thread = Thread.currentThread();
    transactionMap.remove(thread);
    this.gtxSet.remove(gtx);
    if (status != Status.STATUS_COMMITTED) {
      switch (cozOfException) {
        case EXCEPTION_IN_NOTIFY_BEFORE_COMPLETION: {
          String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_TRANSACTION_ROLLED_BACK_BECAUSE_OF_EXCEPTION_IN_NOTIFYBEFORECOMPLETION_FUNCTION_CALL_ACTUAL_EXCEPTION_0.toLocalizedString();
          LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
          if (VERBOSE) writer.fine(exception, e);
          RollbackException re = new RollbackException(exception);
          re.initCause(e);
          throw re;
        }
        case MARKED_ROLLBACK: {
          String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_COMMIT_TRANSACTION_ROLLED_BACK_BECAUSE_A_USER_MARKED_IT_FOR_ROLLBACK.toLocalizedString();
          LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
          if (VERBOSE) writer.fine(exception, e);
          throw new RollbackException(exception);
        }
        case COMMIT_FAILED_SO_ROLLEDBAK: {
          LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
          if (VERBOSE) writer.fine(e);
          throw (RollbackException) e;
        }
        case COMMIT_FAILED_ROLLBAK_ALSO_FAILED:
        case ROLLBAK_FAILED: {
          LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
          if (VERBOSE) writer.fine(e);
          throw (SystemException) e;
        }
      }
    }
    gtx.setStatus(Status.STATUS_NO_TRANSACTION);
  }

  /**
   * Rolls back the transaction associated with the current thread by calling
   * the GlobalTransaction.rollback(). When this method completes, the thread
   * is no longer associated with a transaction.
   * 
   * @throws java.lang.SecurityException - Thrown to indicate that the thread
   *             is not allowed to commit the transaction.
   * @throws java.lang.IllegalStateException - Thrown if the current thread is
   *             not associated with a transaction.
   * @throws SystemException - Thrown if the transaction manager encounters an
   *             unexpected error condition.
   * 
   * @see javax.transaction.TransactionManager#commit()
   */
  public void rollback() throws IllegalStateException, SecurityException,
      SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
//    boolean isRollingBack = false;
    LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
    Transaction transactionImpl = getTransaction();
    if (transactionImpl == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_ROLLBACK_NO_TRANSACTION_EXISTS.toLocalizedString();
      if (VERBOSE) writer.fine(exception);
      throw new IllegalStateException(exception);
    }
    GlobalTransaction gtx = getGlobalTransaction(transactionImpl);
    if (gtx == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_ROLLBACK_NO_GLOBAL_TRANSACTION_EXISTS.toLocalizedString();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    int status = gtx.getStatus();
    if (!(status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK)) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_ROLLBACK_TRANSACTION_STATUS_DOES_NOT_ALLOW_ROLLBACK_TRANSACTIONAL_STATUS_0.toLocalizedString(Integer.valueOf(status));
      if (VERBOSE) writer.fine(exception);
      throw new IllegalStateException(exception);
    }
    //ensure only one thread proceeds from here
    status = -1;
    synchronized (gtx) {
      if ((status = gtx.getStatus()) == Status.STATUS_ACTIVE
          || status == Status.STATUS_MARKED_ROLLBACK)
        gtx.setStatus(Status.STATUS_ROLLING_BACK);
      else if (gtx.getStatus() == Status.STATUS_ROLLING_BACK) {
        String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_ROLLBACK_TRANSACTION_ALREADY_IN_A_ROLLING_BACK_STATE_TRANSACTIONAL_STATUS_0.toLocalizedString(Integer.valueOf(status));
        if (VERBOSE) writer.fine(exception);
        throw new IllegalStateException(exception);
      }
      else {
        String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_ROLLBACK_TRANSACTION_STATUS_DOES_NOT_ALLOW_ROLLBACK.toLocalizedString();
        if (VERBOSE) writer.fine(exception);
        throw new IllegalStateException(exception);
      }
    }
    //Only one thread can call rollback (the first thread to do reach the
    // block above).
    //Before rollback the notifications to be done before the done are called
    // the global transaction is called and then the after completion
    // notifications
    // are taken care of. The transactions associated to the global
    // transactions are
    //removed from the map and also the tread to transaction.
    //
    //TODO remove all threads-transactions (from the map)
    //for transactions participating in the global transaction
    //
    SystemException se = null;
    try {
      gtx.rollback();
    }
    catch (SystemException se1) {
      se = se1;
    }
    try {
      ((TransactionImpl) transactionImpl)
          .notifyAfterCompletion(gtx.getStatus());
    }
    catch (Exception e1) {
      if (writer.infoEnabled())
          writer.info(LocalizedStrings.TransactionManagerImpl_EXCEPTION_IN_NOTIFY_AFTER_COMPLETION_DUE_TO__0, e1.getMessage(), e1);
    }
    Thread thread = Thread.currentThread();
    transactionMap.remove(thread);
    this.gtxSet.remove(gtx);
    if (se != null) {
      if (VERBOSE) writer.fine(se);
      throw se;
    }
    gtx.setStatus(Status.STATUS_NO_TRANSACTION);
  }

  /**
   * Set the Global Transaction status (Associated with the current thread) to
   * be RollBackOnly
   * 
   * Becauce we are using one phase commit, we are not considering Prepared and
   * preparing states.
   * 
   * @see javax.transaction.TransactionManager#setRollbackOnly()
   */
  public void setRollbackOnly() throws IllegalStateException, SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    GlobalTransaction gtx = getGlobalTransaction();
    if (gtx == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_SETROLLBACKONLY_NO_GLOBAL_TRANSACTION_EXISTS.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    synchronized (gtx) {
      int status = gtx.getStatus();
      if (status == Status.STATUS_ACTIVE)
        gtx.setRollbackOnly();
      else if (status == Status.STATUS_COMMITTING)
        gtx.setStatus(Status.STATUS_ROLLING_BACK);
      else if (status == Status.STATUS_ROLLING_BACK)
        ; //Dont do anything
      else {
        String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_SETROLLBACKONLY_TRANSACTION_CANNOT_BE_MARKED_FOR_ROLLBACK_TRANSCATION_STATUS_0.toLocalizedString(Integer.valueOf(status));
        LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
        if (VERBOSE) writer.fine(exception);
        throw new IllegalStateException(exception);
      }
    }
    //Asif : Log after exiting synch block
    LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
    if (VERBOSE) writer.fine("Transaction Set to Rollback only");
  }

  /**
   * Get the status of the global transaction associated with this thread
   * 
   * @see javax.transaction.TransactionManager#getStatus()
   */
  public int getStatus() throws SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    GlobalTransaction gtx = getGlobalTransaction();
    if (gtx == null) {
      return Status.STATUS_NO_TRANSACTION;
    }
    return gtx.getStatus();
  }

  /**
   * not supported
   * 
   * @see javax.transaction.TransactionManager#setTransactionTimeout(int)
   */
  public void setTransactionTimeout(int seconds) throws SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    GlobalTransaction gtx = getGlobalTransaction();
    if (gtx == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_SETTRANSACTIONTIMEOUT_NO_GLOBAL_TRANSACTION_EXISTS.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    long newExpiry = gtx.setTransactionTimeoutForXARes(seconds);
    if(newExpiry > 0 ){
    //long expirationTime = System.currentTimeMillis() + (seconds * 1000);
    gtxSet.remove(gtx);
    //  Asif :Lets blindly remove the current gtx from the TreeMap &
    // Add only if status is neither Rolledback, Unknown , committed or no
    // transaction or GTX not
    // expired, which gurantees that the client thread will be returning &
    // cleaning up .so we
    //don't add it
    int status = gtx.getStatus();
    if (status != Status.STATUS_NO_TRANSACTION
        && status != Status.STATUS_COMMITTED
        && status != Status.STATUS_ROLLEDBACK && !gtx.isExpired()) {
      //Asif : Take a lock on GTX while setting the new Transaction timeout
      // value,
      // so that cleaner thread sees the new value immediately else due to
      // volatility issue
      // we may have inconsistent values of time out
      boolean toAdd = false;
      synchronized (gtx) {
        if (!gtx.isExpired()) {
          gtx.setTimeoutValue(newExpiry);
          toAdd = true;
        }
      }
      //Asif : It is possible that in the window between we set the new
      //timeout value in current GTX & add it to the gtxSet , the currentGtx
      //is expired by the cleaner thread as there is no safeguard for it.
      //We allow it to happen that is add the expired GTx to the TreeSet.
      //Since a notify will be issued , the cleaner thread wil take care
      // of it.
      if (toAdd) {
        synchronized (gtxSet) {
          gtxSet.add(gtx);
          if (gtxSet.first() == gtx) {
            gtxSet.notify();
          }
        }
      }
    }
    else {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_SETTRANSACTIONTIMEOUT_TRANSACTION_HAS_EITHER_EXPIRED_OR_ROLLEDBACK_OR_COMITTED.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    }
  }

  /**
   * @see javax.transaction.TransactionManager#suspend()
   */
  public Transaction suspend() throws SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    Transaction txn = getTransaction();
    
    if(null != txn) {
      GlobalTransaction gtx = getGlobalTransaction(txn);
      gtx.suspend();
      transactionMap.remove(Thread.currentThread());
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (writer.infoEnabled())
          writer.info(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPLSUSPENDTRANSACTION_SUSPENDED);
    }

    return txn;
  }

  /**
   * @see javax.transaction.TransactionManager#resume(javax.transaction.Transaction)
   */
  public void resume(Transaction txn) throws InvalidTransactionException,
      IllegalStateException, SystemException {
    if (!isActive) { throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); }
    if (txn == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_RESUME_CANNOT_RESUME_A_NULL_TRANSACTION.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new InvalidTransactionException(exception);
    }
    GlobalTransaction gtx = getGlobalTransaction(txn);
    if (gtx == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_RESUME_CANNOT_RESUME_A_NULL_TRANSACTION.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new InvalidTransactionException(exception);
    }
    gtx.resume();
    try {
      Thread thread = Thread.currentThread();
      transactionMap.put(thread, txn);
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (writer.infoEnabled())
          writer.info(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPLRESUMETRANSACTION_RESUMED);
    }
    catch (Exception e) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_RESUME_ERROR_IN_LISTING_THREAD_TO_TRANSACTION_MAP_DUE_TO_0.toLocalizedString(e);
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
  }

  /**
   * Get the transaction associated with the calling thread
   * 
   * @see javax.transaction.TransactionManager#getTransaction()
   */
  public Transaction getTransaction() throws SystemException {
    if (!isActive) { 
      throw new SystemException(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGER_INVALID.toLocalizedString()); 
    }
    Thread thread = Thread.currentThread();
    Transaction txn = (Transaction) transactionMap.get(thread);
    return txn;
  }

  /**
   * Get the Global Transaction associated with the calling thread
   */
  GlobalTransaction getGlobalTransaction() throws SystemException {
    Transaction txn = getTransaction();
    if (txn == null) {
      return null;
    }
    GlobalTransaction gtx = (GlobalTransaction) globalTransactionMap.get(txn);
    return gtx;
  }

  /**
   * Get the Global Transaction associated with the calling thread
   */
  GlobalTransaction getGlobalTransaction(Transaction txn)
      throws SystemException {
    if (txn == null) {
      String exception = LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPL_GETGLOBALTRANSACTION_NO_TRANSACTION_EXISTS.toLocalizedString();
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (VERBOSE) writer.fine(exception);
      throw new SystemException(exception);
    }
    GlobalTransaction gtx = (GlobalTransaction) globalTransactionMap.get(txn);
    return gtx;
  }

  //Asif : This method is used only for testing purposes
  Map getGlobalTransactionMap() {
    return globalTransactionMap;
  }

  //Asif : This method is used only for testing purposes
  Map getTransactionMap() {
    return transactionMap;
  }

  //Asif : Remove the mapping of tranxn to Global Tranxn.
  //Previously thsi task was being done in GlobalTranxn
  void cleanGlobalTransactionMap(List tranxns) {
    synchronized (tranxns) {
      Iterator iterator = tranxns.iterator();
      while (iterator.hasNext()) {
        globalTransactionMap.remove(iterator.next());
      }
    }
  }

  void removeTranxnMappings(List tranxns) {
    Object[] threads = transactionMap.keySet().toArray();
    int len = threads.length;
    Object tx = null;
    boolean removed = false;
    Object temp = null;
    for (int i = 0; i < len; ++i) {
      tx = transactionMap.get((temp = threads[i]));
      removed = tranxns.remove(tx);
      if (removed) {
        transactionMap.remove(temp);
        globalTransactionMap.remove(tx);
      }
    }
  }

  class TransactionTimeOutThread implements Runnable {

    protected volatile boolean toContinueRunning = true;

    public void run() {
      GlobalTransaction currentGtx = null;
      long lag = 0;
      LogWriterI18n logger = TransactionUtils.getLogWriterI18n();

      while (toContinueRunning) { //Asif :Ensure that we do not get out of
        try { // wait
          //without a GTX object
          synchronized (gtxSet) {
            while (gtxSet.isEmpty() && toContinueRunning) {
              gtxSet.wait();
            }
            if (!toContinueRunning) continue;
            currentGtx = (GlobalTransaction) gtxSet.first();
          }
          //Asif : Check whether current GTX has timed out or not
          boolean continueInner = true;
          do {
            synchronized (currentGtx) {
              lag = System.currentTimeMillis() - currentGtx.getExpirationTime();
              if (lag >= 0) {
                //Asif: Expire the GTX .Do not worry if some GTX comes
                // before it in Map , ie
                // even if tehre is a GTX earlier than this one to expire ,
                // it is OK
                // to first take care of this one
                //TODO: Do the clean up from all Maps
                currentGtx.expireGTX();
                gtxSet.remove(currentGtx);
              }
            }
            synchronized (gtxSet) {
              if (gtxSet.isEmpty()) {
                continueInner = false;
              }
              else {
                currentGtx = (GlobalTransaction) gtxSet.first();
                boolean isGTXExp = false;
                //Asif: There will not be any need for synchronizing
                //  on currentGTX as we are already taking lokc on gtxSet.
                // Since the GTXSEt is locked that implies no GTX can be
                // either removed or added ( if already removed)
                //if the thread is in this block . thus we are safe
                //
                //synchronized (currentGtx) {
                lag = System.currentTimeMillis() - currentGtx.getExpirationTime();
                if (lag < 0) {
                  //Asif : Make the thread wait for stipluated
                  // time
                  isGTXExp = false;
                }
                else {
                  isGTXExp = true;
                  //Asif It is possibel that GTX is already expired but
                  // just got added in TreeSet bcoz of the small window
                  // in setTimeOut function of TranxnManager.
                  if (!currentGtx.isExpired()) {
                    currentGtx.expireGTX();
                  }
                  gtxSet.remove(currentGtx);
                  //Asif Clean the objects
                  if (gtxSet.isEmpty()) {
                    continueInner = false;
                  }
                  else {
                    currentGtx = (GlobalTransaction) gtxSet.first();
                  }
                }
                //}
                //Asif : It is OK if we release the lock on Current GTX
                // for sleep
                //bcoz as we have still the the lock on gtxSet, even if
                // the
                //transaction set time out gets modified by other client
                // thread,
                // it will notbe added to the set as the lock is still
                // with us.
                //So in case of new tiemout value, if it is such that
                // the GTX is
                /// at the beginning , notified will be issued & the
                // cleaner thread
                // wil awake.
                // the set as the lock is
                if (!isGTXExp && toContinueRunning) {
                  gtxSet.wait(-(lag));
                  if (gtxSet.isEmpty()) {
                    continueInner = false;
                  } else {
                    currentGtx = (GlobalTransaction) gtxSet.first();
                  }
                }
                if (!toContinueRunning) {
                  continueInner = false;
                }
              }
            } // synchronized
          } while (continueInner);
        }
        catch (InterruptedException e) {
          // No need to reset the bit; we'll exit.
          if (toContinueRunning) {
            logger.fine("TransactionTimeOutThread: unexpected exception", e);
          }
          return; 
        }
        catch (CancelException e) {
          // this thread is shutdown by doing an interrupt so this is expected
          //logger.fine("TransactionTimeOutThread: encountered exception", e);
          return;
        }
        catch (Exception e) {
          if (logger.severeEnabled() && toContinueRunning) {
            logger.severe(
              LocalizedStrings.TransactionManagerImpl_TRANSACTIONTIMEOUTTHREAD__RUN_EXCEPTION_OCCURRED_WHILE_INSPECTING_GTX_FOR_EXPIRY, e);
          }
        }
      }
    }
  }

  static class GlobalTransactionComparator implements Comparator {

    /**
     * Sort the array in ascending order of expiration times
     */
    public int compare(Object obj1, Object obj2) {
      GlobalTransaction gtx1 = (GlobalTransaction) obj1;
      GlobalTransaction gtx2 = (GlobalTransaction) obj2;
      return gtx1.compare(gtx2);
    }

    /**
     * Overwrite default equals implementation
     */
    @Override
    public boolean equals(Object o1) {
      return this == o1;
    }
  }

  /**
   * Shutdown the transactionManager and threads associated with this.
   */
  public static void refresh() {
    getTransactionManager();
    transactionManager.isActive = false;
    transactionManager.cleaner.toContinueRunning = false;
    try {
      transactionManager.cleanUpThread.interrupt();
    }
    catch (Exception e) {
      LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
      if (writer.infoEnabled())
          writer
              .info(LocalizedStrings.TransactionManagerImpl_TRANSACTIONMANAGERIMPLCLEANUPEXCEPTION_WHILE_CLEANING_THREAD_BEFORE_RE_STATRUP);
    }
/*    try {
      transactionManager.cleanUpThread.join();
    }
    catch (Exception e) {
      e.printStackTrace();
    }*/
    transactionManager = null;
  }
}
